/**
 * File API 规格定义了网页应用中的文件对象，允许通过编程方式访问和选择，整个File API 包括下面五大接口：
 *    FileList 接口：表示一个从操作系统中选择的文件组成的数组。当 input 元素处于 file upload 状态时可以被调用。
 *    Blob 接口：表示不可变的原始二进制数据，并且允许以分离的二进制数据的形式访问 Blob 对象中字节范围。
 *    File 接口：包含了一个文件的只读信息和属性，比如文件名、最近修改时间等。
 *    FileReader 接口：定义了读取文件和二进制数据的方法，并以事件的形式给出结果。
 *    URL 方案：为二进制数据定义的 URL 方案，以便在网页应用内部引用它们。
 * 以上接口涉及的方法几乎都是异步的，实际上规格也定义了同步版本，但不推荐使用。
 */
// 下面是一个简单但完备的实例，包括进度、错误处理、和成功回调，且通过事件句柄实现代码分离
const startRead = () => {
  let file = document.getElementById('file').files[0];
  if(file) {
    console.log(file);
    getAsTest(file); // 文本形式读取文件
  }
}

// 以文本形式读取文件，并绑定事件句柄
const getAsTest = (file) => {
  let reader = new FileReader();
  // 以 UTF-16 编码形式读取文件文本到内存
  reader.readAsText(file, 'gb2312');
  // 以事件形式处理读取回调
  reader.onloadstart = () => {
    console.log(reader.readyState);
    console.log('load start');
  }
  reader.onprogress = updateProgress;
  reader.onload = loaded;
  reader.onabort = () => {
    console.log('load abord');
  }
  reader.onerror = errorHandler;
  reader.onloadend = () => {
    console.log(reader.readyState);
    console.log('load end');
  }
}

// 进度处理
const updateProgress = (evt) => {
  if(evt.lengthComputable) {
    // evt.loaded 和 evt.total 是进度事件属性，表示已加载内容大小和总大小
    let loaded = (evt.loaded / evt.total);
    if(loaded <= 1) {
      console.log(`进度：${loaded}`);
    }
  }
}

// 加载完成
const loaded = (evt) => {
  // 获取文件数据
  let fileString = evt.target.result;
  // 可以进行预处理、编译、编辑、发送到远端等等操作
  console.log(fileString);
}

// 错误处理
const errorHandler = (evt) => {
  if(evt.target.error.name === 'NotReadableError') {
    throw new Error('The File can not be read');
  }
  console.log(evt.target.error.name);
}

// 绑定 input 元素的 change 事件，触发获取文件逻辑
document.getElementById('file').addEventListener('change', () => {
  // startRead();
  blobUrl();
})

/**
 * Blob 接口（Binary large object）：定义了 File API 二进制数据的存储结构，File 接口继承自该接口
 *    new Blob(blobPart, BlobPropertyBag) 构造函数接收两个参数：
 * blobPart: 本质是需要一个二进制序列，BufferSource 元素、另一个 Blob 对象、USVString 元素都可以满足
 * BlobPropertyBag 字典： {
 *    type: ASCII编码的小写字符串，表示该 Blob 的媒体类型（MIME）
 *    endings: 枚举值 {
 *        "transparent": 默认，行尾透明
 *        "native": 使用 USVString 作为 blobPart 时使行尾转换为本地
 *    }
 * }
 * 
 * Blob 字典： {
 *    size: {unsigned long long}，只读，返回字节序列的可读字节数，否则为0；
 *    type: {string}，只读，Blob 对象的媒体类型（MIME）
 *    slice: {Function(Number start, Number end, string )} 返回一个新的 Blob 对象，其字节范围从原数据的 start 位置开始，到 end 位置结束，type 为给出的 MIME 类型。
 * }
 */
// 创建一个 1Kb ArrayBuffer
let buffer = new ArrayBuffer(1024);
// 基于上面的 ArrayBuffer 创建一个 ArrayBufferView 对象
let shorts = new Uint16Array(buffer, 512, 128);
let bytes = new Uint8Array(buffer, shorts.byteOffset + shorts.byteLength);
// 创建 Blob 对象
let b = new Blob([
  "foobarbazetcetc" + '阿斯蒂芬更何况'
], {
  type: 'text/plain;charset=utf-8'
});
console.log(b);
let c = new Blob([b, shorts]);
console.log(c);
console.log(b.slice(0, -7, 'text/plain;charset=utf-8'));

/**
 * File 接口：File 对象是一个附加了 name 属性的 Blob。可通过构造函数创建，也可以通过底层文件系统引用字节序列获得。
 * 如果 FIile 对象引用的字节序列来自桌面文件，那么它的“状态快照”应该在 File 对象创建时被设置成那个文件的状态。
 *    File(fileBits, fileName, [, BlobPropertyBag])
 * fileBits: 包含任意顺序和任意数量的 BufferSource、Blob、USVString元素的数组。
 * fileName: {USVString} 文件名 
 * BlobPropertyBag: 与 Blob 类似，但多出一个 lastModified: 指定最近修改时间
 * 
 * File 实例字典： {
 *    具有 Blob 对象所有的属性和方法，除此之外：
 *    name: {string}，只读，文件名
 *    lastModified: {long long}，只读，文件的最近修改时间。
 * }
 * // 获得 File 对象实例，可以通过构造函数创建，也可是从 input 元素中获取
 */
let date = new Date('2013/12/05 23:45:16');
let generateFile = new File([b], "Draft1.txt", {
  type: 'text/plain',
  lastModified: date
})
console.log(generateFile);

/**
 * FileList 接口：File 对象组成的列表（伪数组）
 * FileList 字典 {
 *     length: 内部 file 对象个数
 *      item: {Function(Number index)} 返回指定下标对应的 File 对象
 * }
 */

/**
 * FileReader 接口，定义了方法和属性读取二进制数据，并以指定结果返回，构造函数没有参数。
 * FileReader 字典： {
 *    onloadstart: 开始加载事件句柄
 *    onprogress: 加载的进度事件句柄
 *    onbort: 终止事件句柄
 *    onerror: 转换过程中异常事件句柄
 *    onload: 加载完成事件句柄（获取结果就在这里）
 *    onloadend: 加载流程结束（在 onload 之后调用）
 *    readyState:
 *        0: EMPTY，FileReader 对象已构建，但没有读取函数被调用，没有待定的读取操作
 *        1: LOADING，文件正在加载中
 *        2: DONE，文件实体已全部放入内存，加载完毕
 *    result: 原二进制对象读取后产生的结果，类型对应下面四种读取函数
 *    下面是四个异步加载方法，可以将指定二进制数据加载成对应的结果，放入内存。
 *    readAsArrayBuffer，readAsBinaryString，readAsText，readAsDataURL
 *    abort: {Function} 终止读取操作
 * }
 */

/**
 * File API 允许在 web Worker 中使用，但只能是同步的 File 或者 Blob 读取方法
 * 与异步相比，同步 API 基本相同，只要 FileReaderSync 接口构建实例即可。
 */

/**
 * Blob URL，是型如 blob:http://example.com/550e8400-e29b-41d4-a716-446655440000 这样的字符串
 * 使用它可以将 Blob 或者 File 对象与某些只能使用 URL 的 web API 结合起来，比如 <img src>。
 * 它也可以用于导航和触发下载本地生成的数据。
 * 为了实现它，标准在 URL 对象上扩展两个方法:
 *    URL.createObjectURL(blob)： 创建一个 URL 和 Blob 的映射
 *    URL.revokeObjectURL(url)：撤销、销毁上述的映射
 * 因为映射的存在使得垃圾回收机制无法清除 blob，这可能会造成内存溢出，因此需要尽可能早的 revokeObjectURL 
 */
const blobUrl = () => {
  let file = document.getElementById('file').files[0];
  let blobURLref = URL.createObjectURL(file);
  let img1 = new Image();
  img1.src = blobURLref;
  img1.onload = (evt) => {
    let img = evt.target;
    document.body.appendChild(img);
    let oA = document.createElement('a');
    oA.download = `yzh_png-${(+new Date()+'').slice(0, 9)}.png`;
    oA.style.display = 'none';
    oA.href = blobURLref;
    document.body.appendChild(oA);
    oA.click();
    document.body.removeChild(oA);
    URL.revokeObjectURL(blobURLref);
  };
}